﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.IO;
using fleXdoc.Api;
using System.Xml.XPath;
using System.Xml;
using System.Globalization;
using System.Threading;
using System.Configuration;

namespace fleXdoc.Service
{
    [ServiceBehavior(Namespace = "urn:fleXdoc", Name="fleXdoc")]
    public class Service : IDocumentBuilder
    {
        private const string APPSETTING_TEMPLATECACHEDISABLED = "TemplateCacheDisabled";

        #region IDocumentBuilder Members

        public Stream BuildDocument(BuildDocumentRequest request)
        {
            if (request == null || String.IsNullOrEmpty(request.Template) || String.IsNullOrEmpty(request.dataXML))
            {
                throw new ArgumentException("BuildDocumentRequest not supplied or invalid");
            }

            CultureInfo renderCulture = Thread.CurrentThread.CurrentUICulture;
            if (!String.IsNullOrEmpty(request.RenderCulture))
            {
                try
                {
                    renderCulture = new CultureInfo(request.RenderCulture);
                }
                catch (Exception)
                {
                    throw new ArgumentException("Unsupported RenderCulture specified: " + request.RenderCulture, "request");
                }
            }

            MemoryStream outputStream = new MemoryStream(); // Not within using, cause WCF needs to access it afterwards

            using (MemoryStream dataXmlStream = new MemoryStream())
            {
                using (StreamWriter sw = new StreamWriter(dataXmlStream))
                {
                    sw.Write(request.dataXML);
                    sw.Flush();
                    dataXmlStream.Capacity = Convert.ToInt32(dataXmlStream.Length); // Release any unused memory
                    dataXmlStream.Seek(0, SeekOrigin.Begin);

                    DocBuilder docBuilder = GetDocBuilder(request.Template);
                    docBuilder.Build(dataXmlStream,
                        request.dataXMLNamespacePrefix,
                        request.dataXMLNamespaceURI,
                        outputStream,
                        renderCulture,
                        (request.Validation == BuildValidation.Enabled));
                }
            }

            outputStream.Seek(0, SeekOrigin.Begin);
            return outputStream; // outputStream cannot be disposed, since WCF still needs it
        }

        //public SupportedOutputFormats GetSupportedOutputFormats()
        //{
        //    SupportedOutputFormats supportedFormats = new SupportedOutputFormats();
        //    supportedFormats.Add("DocX");

        //    fleXdocConfig config = ConfigurationManager.GetSection("fleXdoc") as fleXdocConfig;
        //    if (config != null)
        //    {
        //        foreach (OutputConvertor convertor in config.OutputConvertors)
        //        {
        //            supportedFormats.Add(convertor.Format);
        //        }
        //    }

        //    return supportedFormats;
        //}

        #endregion

        private static Dictionary<string, DocBuilder> _docBuilderCache = new Dictionary<string, DocBuilder>();
        private static object _docBuilderCacheLock = new object();

        private DocBuilder GetDocBuilder(string templateName)
        {
            DocBuilder docBuilder = null;

            // Try to get the docbuilder from cache
            if ((!_docBuilderCache.TryGetValue(templateName, out docBuilder)) // Not found in cache
                || (docBuilder == null)) // Found in cache, but null
            {
                // Create a new docbuilder
                string templatesFolder = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates");
                string templatePath = Path.Combine(templatesFolder, templateName);
                string templateFolder = Path.GetDirectoryName(templatePath);
                DocBuilder docBuilderNew = new DocBuilder(templatePath);
                docBuilderNew.ResourceUriResolve += delegate(object sender, ResolveResourceUriEventArgs args)
                {
                    return Path.Combine(templateFolder, args.ResourceUri);
                };

                // Check if cache is disabled (appsetting)
                bool cacheDisabled = false;
                bool.TryParse(ConfigurationManager.AppSettings[APPSETTING_TEMPLATECACHEDISABLED], out cacheDisabled);

                if (!cacheDisabled)
                {
                    // Attempt to add to the cache
                    lock (_docBuilderCacheLock) // Keeping the lock as short as possible
                    {
                        // Check again, because may have been changed while waiting for the lock
                        if ((!_docBuilderCache.TryGetValue(templateName, out docBuilder))
                            || (docBuilder == null))
                        {
                            // Remove existing entry (if any, just for safety)
                            if (_docBuilderCache.ContainsKey(templateName))
                            {
                                _docBuilderCache.Remove(templateName);
                            }

                            _docBuilderCache.Add(templateName, docBuilderNew);
                            docBuilder = docBuilderNew;
                        }
                        else
                        {
                            // Throw it away
                            ((IDisposable)docBuilderNew).Dispose();
                            docBuilderNew = null;
                        }
                    }
                }
                else
                {
                    // Cache disabled, so always return the newly created docBuilder
                    docBuilder = docBuilderNew;
                }
            }

            return docBuilder;
        }
    }
}
